/*
 * timer.c
 *
 *  Created on: Nov 23, 2011
 *  Author: Efim Todorasco
 */
#include <semaphore.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "Queue.h"
#include "process.h"
#include "processRequest.h"
#include "pcb.h"
#include <string.h>
#include <time.h>

Queue interruptQueue;
Queue readyQueue;
Queue diskQueue;
Queue videoQueue;
Process *currentProcessPtr;
Process *idleProcessPtr;
void *timer(void *ptr);
void *cpu_control(void *ptr);
void *disk_control(void *ptr);
void *video_control(void *ptr);
void ioIntHandler();
void semDownTrap();
void semUpTrap();
void memWrTrap();
void memRdTrap();
void timer_interupt_handler();
void cpu_preemt();
void scheduler();
void consumerRequests();
void producerRequests();
void createConcumerProcess(void *inter);
void createProducerProcess(void *inter);

void diskInt();
void prodInt();
void conInt();
void keyInt();
void calcInt();
void vidInt();

Process* create_regular_process();
pthread_t timer_thread;
pthread_t cpu_thread;
pthread_t disk_thread;
pthread_t video_thread;

char *data[100];
int consumerTotal;
int counter;
int cStep;
int pStep;

sem_t sharedMemory;


pthread_mutex_t lock_interupts;
pthread_mutex_t lockPCSemaphore;


//int sema_init(sema_t *sp, unsigned int count, int type, void *arg);
/******************************************************************************
 *
 *
 *
 * Process:
 *
 *
 ******************************************************************************/
int main (int argc, char *argv[]) {
    if (sem_init(&sharedMemory, 0, 1)) {
        printf("Could not initialize a semaphore\n");
        return -1;
    }
    consumerTotal = 0;

	int i;
	for (i = 0; i < argc; i++) {
		int x = atoi(argv[i]);
		printf("argv[%d] = %s\n", argc, argv[i]);
		fflush(stdout);
		printf("x = %d\n", x);
		fflush(stdout);
	}

	Process *r = createNewProcess();
	//printf("r->pcBlock = %d\n", r->pcBlock->pid);
	//fflush(stdout);
	Process *s = createNewProcess();
	//printf("s->pcBlock = %d\n", s->pcBlock->pid);
	//fflush(stdout);
	queue_init(&interruptQueue);
	queue_init(&readyQueue);
	queue_init(&diskQueue);
	queue_init(&videoQueue);

	pthread_t timer_thread;
	pthread_t cpu_thread;
	pthread_create(&timer_thread, NULL, timer, NULL);
	idleProcessPtr = createNewProcess();
	idleProcessPtr->numOfInstruction = 1000000;
	queue_push(&readyQueue, &r);
	queue_push(&readyQueue, &s);
	queue_push(&readyQueue, &idleProcessPtr);
	/*Process* test2 = (Process*) *(int*) queue_front(&readyQueue);
	printf("%d\n", test2->numOfInstruction);*/
	currentProcessPtr = (Process*) *(long*) queue_front(&readyQueue);
	queue_pop(&readyQueue, 0);

	//pthread_create(&disk_thread, NULL, disk_control, NULL);
	//pthread_create(&video_thread, NULL, video_control, NULL);
	pthread_create(&cpu_thread, NULL, cpu_control, NULL);
	sem_destroy(&sharedMemory);
    pthread_exit(NULL);
    sleep(1000); // supposed to help with printf problems.
}


void *timer(void *ptr) {
	sys_call t = TIMER;
	while (1)
	{
		usleep(3000000);
        //pthread_mutex_lock (&lock_interupts);
		queue_push(&interruptQueue, &t);
		//fflush(stdout);			//stupid flush
		//pthread_mutex_unlock (&lock_interupts);
	}
   // pthread_exit(NULL);
    // return 0;
}

//  Think this works
void *cpu_control(void *ptr) {
	while(1) {
		if (queue_size(&interruptQueue)) {  //if queue is not empty
			currentProcessPtr->curInterupt = *(sys_call*) queue_front(&interruptQueue);
			scheduler();
		} else if(currentProcessPtr == idleProcessPtr && queue_size(&readyQueue))  { // running idle with ready processes.
			queue_push(&readyQueue, &currentProcessPtr);
			currentProcessPtr = queue_front(&readyQueue);
			queue_pop(&readyQueue, 0);
		} else { // no interrupt do process
			incProcess(currentProcessPtr);
			if (currentProcessPtr->curInterupt != NONE) {
				queue_push(&interruptQueue, &currentProcessPtr->curInterupt);
			}
		}
	}
}

void *disk_control(void *ptr) {
	int diskDelay = 100000;
	int currentDisk = diskDelay;
	sys_cal d = DISK_READY;
	while (1)
	{
		if (currentDisk == 0) {
			if (queue_size(&diskQueue)) {
				queue_push(&interruptQueue, &d);
			}
			diskDelay = currentDisk = 100000;
		} else {
			currentDisk--;
		}
	}
    pthread_exit(NULL);
}

void *video_control(void *ptr) {
	int videoDelay = 100000;
	int currentVideo = videoDelay;
	sys_cal v = VIDEO_READY;
	while (1)
	{
		if (currentVideo == 0) {
			if (queue_size(&diskQueue)) {
				queue_push(&interruptQueue, &v);
			}
			videoDelay = currentVideo = 100000;
		} else {
			currentVideo--;
		}
	}
    pthread_exit(NULL);
}

void *producer_control(void *ptr) {
	sys_cal v = PRODUCER;
	if (cStep == 0) {
		if (pStep == 0) {
			pthread_mutex_lock (&lockPCSemaphore);
			queue_push(&interruptQueue, &v);
			pStep++;
		} else if (pStep == 1) {
			queue_push(&interruptQueue, &v);
			sem_post(&sharedMemory);
			pStep++;
		} else if (pStep == 2) {
			queue_push(&interruptQueue, &v);
			data[pStep] = (int) pStep;
			pStep++;
		} else if (pStep == 3) {
			queue_push(&interruptQueue, &v);
			sem_trywait(&sharedMemory);
			pStep++;
		} else if (pStep == 4) {
			v = PRODUCER_FREE;
			queue_push(&interruptQueue, &v);
			pthread_mutex_unlock (&lockPCSemaphore);
			pStep++;
		}
	}
    pthread_exit(NULL);
}

void *consumer_control(void *ptr) {
	sys_cal v = CONSUMER;
	if (pStep == 0) {
		if (cStep == 0) {
			pthread_mutex_lock (&lockPCSemaphore);
			queue_push(&interruptQueue, &v);
			cStep++;
		} else if (cStep == 1) {
			sem_trywait(&sharedMemory);
			queue_push(&interruptQueue, &v);
			cStep++;
		} else if (cStep == 2) {
			consumerTotal = (int) data[cStep];
			queue_push(&interruptQueue, &v);
			cStep++;
		} else if (cStep == 3) {
			sem_post(&sharedMemory);
			queue_push(&interruptQueue, &v);
			cStep++;
		} else if (cStep == 4) {
			v = CONSUMER_FREE;
			pthread_mutex_unlock (&lockPCSemaphore);
			queue_push(&interruptQueue, &v);
			cStep++;
		}
	}
    pthread_exit(NULL);
}


//******the process is mallocing its size,
//		but do we need to malloc when adding to the queue?*********
void scheduler() {
	// get interrupt event
		switch (currentProcessPtr->curInterupt){//(*(sys_call*) queue_front(&interruptQueue)) {
			case DISK:
				queue_push(&diskQueue, &currentProcessPtr);
				currentProcessPtr = queue_front(&readyQueue);
				queue_pop(&diskQueue, 0);
				if (currentProcessPtr == idleProcessPtr && queue_size(&readyQueue)) {
					// queue_pop(&readyQueue, 0);
					queue_push(&readyQueue, &currentProcessPtr);
					currentProcessPtr = queue_front(&readyQueue);
					queue_pop(&readyQueue, 0);
				}
				break;
			case PRODUCER:

				if (pStep < 5 && cStep == 0) {
					prodInt();
				} else if (pStep == 5 && cStep == 0) {
					pStep = 0;
				}
				break;
			case CONSUMER:
				if (cStep < 5 && pStep == 0) {
					conInt();
				} else if (cStep == 5 && pStep == 0) {
					pStep = 0;
				}
				break;
			case KEYBOARD:
				keyInt();
				break;
			case CALCULATOR:
				calcInt();
				break;
			case VIDEO:
				queue_push(&videoQueue, &currentProcessPtr);
				currentProcessPtr = queue_front(&readyQueue);
				if (currentProcessPtr == idleProcessPtr && queue_size(&readyQueue) > 1) {
					queue_pop(&readyQueue, 0);
					queue_push(&readyQueue, &currentProcessPtr);
					currentProcessPtr = queue_front(&readyQueue);
					queue_pop(&readyQueue, 0);
				}
				break;
			case TIMER:
				printf("SCHED Timer call.\n");
				fflush(stdout);			//stupid flush
				if (queue_size(&readyQueue) > 1) {
					// at least one process that is not idle
					// get new process
					queue_push(&readyQueue, &currentProcessPtr);

					printf("Current Process id %d\n", currentProcessPtr->pcBlock->pid);
					fflush(stdout);
					usleep(1000);
					currentProcessPtr = (Process*) *(int*) queue_front(&readyQueue); // <---
					// asm instruction 4019f0 happens after the queue_front but before queue_pop
					// I think that queue_front causes the access error.
					queue_pop(&readyQueue, 0);
				} else { // size is 1 so idle process is only one ready.
					// restore pcb
					currentProcessPtr->pcBlock->state = READY;
				}
				queue_pop(&interruptQueue, 0);
				queue_push(&readyQueue, &currentProcessPtr);
				break;
			case DONE:
				printf("process done\n");
				fflush(stdout);
				currentProcessPtr = (Process*) *(long*) queue_front(&readyQueue);
				queue_pop(&readyQueue, 0);

				break;
			default:
				break;
		}
	currentProcessPtr->curInterupt = NONE;
}

void diskInt() {
	//usleep(rand()%10000);
	currentProcessPtr->pcBlock->state = BLOCKED;
	scheduler();
}

void prodInt() {

}

void conInt() {

}

void keyInt() {

}

void calcInt() {

}

void vidInt() {
	currentProcessPtr->pcBlock->state = BLOCKED;
	scheduler();
}


void cpu_preemt() {
	// SAVE TO PCB
	currentProcessPtr->pcBlock->state = INTERRUPT;
	currentProcessPtr->pcBlock->next_step += currentProcessPtr->curInstruction;
}
Process* create_regular_process(){
	Process *p = createNewProcess();

	return p;
}
void ioIntHandler() {
	pthread_mutex_lock (&lock_interupts);
	//scheduler();
	pthread_mutex_unlock (&lock_interupts);
}

void semDownTrap() {

}

void semUpTrap() {

}

void memWrTrap() {

}

void memRdTrap() {

}

void timer_interupt_handler() {
	//save to pcb
    pthread_mutex_lock (&lock_interupts);
	scheduler();
    pthread_mutex_unlock (&lock_interupts);

}


void createDiskIOprocess(void *inter) {
	Process* p = createNewProcess();
	p->curInterupt = DISK;
	p->pcBlock->state = READY;
	queue_push(&readyQueue, &p);
}

void createVideoIOprocess(void *inter) {
	Process* p = createNewProcess();
	p->curInterupt = VIDEO;
	p->pcBlock->state = READY;
	queue_push(&readyQueue, &p);
}

void createKeyboardIOprocess(void *inter) {
	Process* p = createNewProcess();
	p->curInterupt = KEYBOARD;
	p->pcBlock->state = READY;
	queue_push(&readyQueue, &p);
}

void createProducerprocess(void *inter) {
	Process* p = createNewProcess();
	p->curInterupt = PRODUCER;
	p->pcBlock->state = READY;
	queue_push(&readyQueue, &p);
}

void createConcumerprocess(void *inter) {
	Process* p = createNewProcess();
	p->curInterupt = CONSUMER;
	p->pcBlock->state = READY;
	queue_push(&readyQueue, &p);
}



void consumerRequests() {
	if (cStep == 0) {
		//call to scheduler to do a pthread_mutex_lock (&lockPCSemaphore);;
		cStep++;
	} else if (cStep == 1) {
		//call to scheduler to do a semaphore_write_down();
		cStep++;
	} else if (cStep == 2) {
		//call to scheduler to do a write();
		cStep++;
	} else if (cStep == 3) {
		//call to scheduler to do a semaphore_read_up();
		cStep++;
	} else if (cStep == 4) {
		//call to scheduler to do a pthread_mutex_unlock (&lockPCSemaphore);
		cStep++;
	}
}

void producerRequests() {
	if (pStep == 0) {
		//call to scheduler to do a pthread_mutex_lock (&lockPCSemaphore);
		pStep++;
	} else if (pStep == 1) {
		//call to scheduler to do a semaphore_read_down();
		pStep++;
	} else if (pStep == 2) {
		//call to scheduler to do a read();
		pStep++;
	} else if (pStep == 3) {
		//call to scheduler to do a semaphore_write_up();
		pStep++;
	} else if (pStep == 4) {
		//call to scheduler to do a pthread_mutex_unlock (&lockPCSemaphore);
		pStep = 0;
	}
}

//CREATE  and test Queue							**WORKING QUEUE
/*  <----- test code keep around
Queue interuptQueue;
queue_init(&interuptQueue);
int test, test2;
test = 5;
queue_push(&interruptQueue, &test);
test2 = *(int*) queue_front(&interruptQueue);
printf("test2 = %d", test2);
fflush(stdout);
*/


/* For allocating a single object of <strong class="highlight">type</strong> T */
// T *p = <strong class="highlight">malloc</strong> ( sizeof *p );

/* For allocating N objects of <strong class="highlight">type</strong> T */
// T *p = <strong class="highlight">malloc</strong> ( N * sizeof *p );

//***A little code to help when we handle main's parameters***
//	char *parameters[100];
//	parameters[0] = "1";
//	parameters[1] = "P";
//	parameters[2] = "2";
//	parameters[3] = "P";
//	parameters[4] = "1";
//	parameters[5] = "P";
//	parameters[6] = "2";
//	parameters[7] = "P";
//	parameters[8] = "1";
//	parameters[9] = "P";
//	parameters[10] = "E";
//	int arrSize = sizeof(parameters);
//
//	printf("array size? %d\n", arrSize);
//	fflush(stdout);
//	int i = 0;
//	while (i < sizeof(parameters)) {
//		if (strcmp(parameters[i], "E") == 0) {
//			break;
//		} else {
//			printf("Parameter %d = %s\n", i, parameters[i]);
//			fflush(stdout);
//			i++;
//		}
//	}
/**case IO_SYS_CALL:
	printf("IO system call for current process.");
	fflush(stdout);			//stupid flush
	currentProcessPtr->curState = INTERUPTED;
	ioIntHandler();
	break;
case SEM_DOWN_SYS_CALL:
	printf("Down on semaphore for current process.");
	fflush(stdout);			//stupid flush
	semDownTrap();
	break;
case SEM_UP_SYS_CALL:
	printf("Up on semaphore for current process.");
	fflush(stdout);			//stupid flush
	semUpTrap();
	break;
case MEM_WRITE_SYS_CALL:
	printf("Memory write call for current process.");
	fflush(stdout);			//stupid flush
	memWrTrap();
	break;
case MEM_READ_SYS_CALL:
	printf("Memory read call for current process.");
	fflush(stdout);			//stupid flush
	memRdTrap();
	break;**/
